package redis

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/go-redis/redis/v7"
	"net"
	"strconv"
	"strings"
	"time"
)

type Client struct {
	*redis.Client
}

type Options struct {
	Username string
	Password string
	// max retry,  not retry = -1,  default, single=0 cluster=8
	MaxRetries int
	// timeout
	DialTimeoutSec  int64 `defval:"60"`
	ReadTimeoutSec  int64 `defval:"300"`
	WriteTimeoutSec int64 `defval:"300"`
}

type Config struct {
	Addr string `defval:"127.0.0.1:6379"`
	DB   int    `defval:"0"`
	Options
}

// NewRedisClient simplify GET SET DEL operation
func NewRedisClient(cfg Config) (*Client, error) {
	cli := redis.NewClient(&redis.Options{
		Addr:         cfg.Addr,
		Username:     cfg.Username,
		Password:     cfg.Password,
		DB:           cfg.DB,
		MaxRetries:   cfg.MaxRetries,
		DialTimeout:  time.Duration(cfg.DialTimeoutSec) * time.Second,
		ReadTimeout:  time.Duration(cfg.ReadTimeoutSec) * time.Second,
		WriteTimeout: time.Duration(cfg.WriteTimeoutSec) * time.Second,
	})
	sCmd := cli.Ping()
	if sCmd.Err() != nil {
		return nil, sCmd.Err()
	}
	return &Client{
		Client: cli,
	}, nil
}

// Get return String value
func (c *Client) Get(key string) (interface{}, error) {
	return c.GetContext(context.Background(), key)
}

func (c *Client) GetContext(ctx context.Context, key string) (interface{}, error) {
	strCmd := c.Client.WithContext(ctx).Get(key)
	if err := strCmd.Err(); err != nil {
		return nil, err
	}
	return strCmd.Val(), nil
}

func (c *Client) Set(key string, value interface{}) error {
	return c.SetContext(context.Background(), key, value, -1)
}

func (c *Client) SetWithTTL(key string, value interface{}, expiration time.Duration) error {
	return c.SetContext(context.Background(), key, value, expiration)
}

func (c *Client) SetContext(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
	sCmd := c.Client.WithContext(ctx).Set(key, value, expiration)
	return sCmd.Err()
}

func (c *Client) Del(key string) error {
	return c.DelContext(context.Background(), key)
}

func (c *Client) DelContext(ctx context.Context, key string) error {
	return c.Client.WithContext(ctx).Del(key).Err()
}

func (c *Client) Lock(key string, ttl time.Duration) (bool, error) {
	nx := c.SetNX("LOCK:"+key, "1", ttl)
	if nx.Val() {
		//表示获取锁成功
		return true, nil
	}
	return false, nil
}

func (c *Client) UnLock(key string) {
	_ = c.Del("LOCK:" + key)
}

func (c *Client) IncrGet(key string) (bool, int64, error) {
	rs, _ := c.Get(key)
	if rs == nil || rs == "" {
		return false, 0, nil
	}
	if r, err := strconv.ParseInt(rs.(string), 10, 64); err != nil {
		return false, 0, err
	} else {
		return true, r, nil
	}
}

func (c *Client) IncrInt64(key string, value int64, ttl time.Duration) error {
	by := c.IncrBy(key, value)
	if by.Err() != nil {
		return by.Err()
	}
	if ttl > 0 {
		_ = c.Expire(key, ttl)
	}
	return nil
}

// ClearFixedKey 删除固定的KEY
func (c *Client) ClearFixedKey(key ...string) error {
	for _, k := range key {
		if err := c.Del(k); err != nil {
			return err
		}
	}
	return nil
}

func (c *Client) ClearLikeKey(key string) error {
	keys := c.Keys(fmt.Sprintf("%v*", key))
	if keys != nil {
		err := c.ClearFixedKey(keys.Val()...)
		if err != nil {
			return err
		}
	}
	return nil
}

func (c *Client) IsNotFundErr(err error) bool {
	if strings.Contains(err.Error(), "redis: nil") {
		return true
	}
	return false
}

// SaveOrGetByJson json字符串的数据处理
func (c *Client) SaveOrGetByJson(key string, out interface{}, isGet bool, ttl time.Duration) (bool, error) {
	//获取缓存数据
	if isGet {
		rs, _ := c.Get(key)
		if rs == nil || rs == "" {
			return false, nil
		}
		return true, toObject(rs.(string), out)
	}
	//设置缓存数据
	val, err := toJson(out)
	if err != nil {
		return false, err
	}
	if ttl > 0 {
		if err := c.SetWithTTL(key, val, ttl); err != nil {
			return false, err
		}
	} else {
		if err := c.Set(key, val); err != nil {
			return false, err
		}
	}
	return true, nil
}

type ClusterConfig struct {
	Addrs []string
	Options
}

type ClusterClient struct {
	*redis.ClusterClient
}

func NewRedisClusterClient(cfg ClusterConfig) (*ClusterClient, error) {
	cli := redis.NewClusterClient(&redis.ClusterOptions{
		Addrs:        cfg.Addrs,
		Username:     cfg.Username,
		Password:     cfg.Password,
		MaxRetries:   cfg.MaxRetries,
		DialTimeout:  time.Duration(cfg.DialTimeoutSec) * time.Second,
		ReadTimeout:  time.Duration(cfg.ReadTimeoutSec) * time.Second,
		WriteTimeout: time.Duration(cfg.WriteTimeoutSec) * time.Second,
	})

	sCmd := cli.Ping()
	if sCmd.Err() != nil {
		return nil, sCmd.Err()
	}
	return &ClusterClient{
		ClusterClient: cli,
	}, nil
}

// MakeUId 根据年月日创建的一个4位的ID
// 20120102000001  14位的操作
func (c *Client) MakeUId(prefix string) (string, error) {
	timeKey := time.Now().Format("20060102")
	key := fmt.Sprintf("%v:%v", prefix, timeKey)
	incr := c.Incr(key)
	if incr.Err() != nil || incr.Val() == 0 {
		return "", fmt.Errorf("create uid error")
	}
	go func() {
		cur := time.Now().Hour()
		if cur == 1 {
			//1点清除原来的昨天的数据
			key := fmt.Sprintf("%v:%v", prefix, time.Now().AddDate(0, 0, -1).Format("20160102"))
			_ = c.Del(key)
		}
	}()
	return fmt.Sprintf("%s%04d", timeKey, incr.Val()), nil
}

// RegRedisId 注册对应的机器ID，最大默认支持1024个节点
func (c *Client) RegRedisId() (int64, error) {
	ip, err := getLocalIP()
	if err != nil {
		return 0, err
	}
	//直接去获取对应IP的
	val := c.HGet("SonyFlakeHash", ip)
	if val.Err() != nil && !c.IsNotFundErr(val.Err()) {
		return 0, err
	}
	s := val.Val()
	if s == "" {
		incr := c.Incr("SonyFlake")
		if incr.Err() != nil || incr.Val() == 0 {
			return 0, err
		}
		_ = c.HSet("SonyFlakeHash", ip, incr.Val())
		return incr.Val(), nil
	}
	rs, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		return 0, err
	}
	return rs, err
}

func getLocalIP() (string, error) {
	conn, err := net.Dial("udp", "8.8.8.8:53")
	if err != nil {
		return "", err
	}
	if updAddr, ok := conn.LocalAddr().(*net.UDPAddr); ok {
		if updAddr.IP.To4() != nil || updAddr.IP.To16() != nil {
			return updAddr.IP.String(), nil
		}
	}
	return addrStringToIP(conn.LocalAddr()), nil
}

func addrStringToIP(addr net.Addr) string {
	str := addr.String()
	if strings.HasPrefix(str, "[") {
		// ipv6
		return strings.Split(strings.TrimPrefix(str, "["), "]:")[0]
	}
	return strings.Split(str, ":")[0]
}

func toJson(v interface{}) (string, error) {
	b, err := json.Marshal(v)
	if err != nil {
		return "", fmt.Errorf("to json error")
	}
	return string(b), nil
}
func toObject(val string, v interface{}) error {
	if err := json.Unmarshal([]byte(val), v); err != nil {
		return fmt.Errorf("json swap error")
	}
	return nil
}
